//-------------------------------------------------------------------------------------------------
//  ray.hpp - Definition of ray class (of finite length) for physics operations
//  (c) 2009 Simon Miessler
//-------------------------------------------------------------------------------------------------

#pragma once

#include <boost/optional.hpp>

#include "math_exports.hpp"
#include "geometry_fwd.hpp"
#include "matrix4x4.hpp"
#include "line.hpp"




namespace math
{
	/**
	 * Contrary to the Line class, Ray represents a line
	 * in 3d-space of finite length.
	 */
	template <typename T>
	class ray
		: public intersectable< ray<T> >
	{
	public:

		/// The used floating-point type
		typedef T   float_t;

	public:

		/// The "start" position of the ray
		vector3<T>   start;

		/// The "end" position of the ray
		vector3<T>   end;

		/// The direction of the ray
		vector3<T>   direction;


		/// The length of the ray
		T            length;

	public:

		/**
		 * Create a Ray.
		 * It is located at (0,0,0) and points to (0,1,0).
		 */
		ray()
			: start(0, 0, 0)
			, end(0, 1, 0)
			, direction(0, 1, 0)
			, length(1)
		{}

		/**
		 * Create a Ray at the specified positions.
		 *
		 * @param start   the start point of the Ray
		 * @param end     the end point of the Ray
		 */
		ray(const vector3<T>& start, const vector3<T>& end)
			: start(start)
			, end(end)
			, direction(end - start)
			, length(direction.length())
		{
			direction /= length;
		}

		/**
		 * Create a Ray at the specified positions.
		 *
		 * @param start       the start point of the Ray
		 * @param end         the end point of the Ray
		 * @param direction   the direction of the Ray
		 * @param length      the length of the Ray
		 */
		ray(const vector3<T>& start, const vector3<T>& end, const vector3<T>& direction, T length)
			: start(start)
			, end(end)
			, direction(direction)
			, length(length)
		{}



		/**
		 * Get a point along the ray.
		 * This function returns a point, even
		 * if t is not between [0,length]. If such a
		 * feature is wished, it must be implemented by
		 * the caller.
		 *
		 * @param t   the distance to the startpoint of the ray
		 */
		vector3<T> point(T t) const
		{
			return start + t * direction;
		}



		/**
		 * Check if the given parameter is valid for
		 * this ray. To pass the test, it must lie
		 * inside [0,length].
		 *
		 * @param t   the parameter (distance to start) to test
		 *
		 * @return true    when t is a valid parameter
		 * @return false   when t is invalid
		 */
		bool isValid(T t) const
		{
			return (t >= 0 && t <= length);
		}



		/**
		 * Calculate the closest ray between this ray and a line.
		 * This function actually returns the parameters for this ray and the line.
		 */
		boost::optional< std::pair<T,T> > closestRayT(const line<T>& l) const
		{
			// Call the implementation of Line vs Line and test if the returned
			// parameter is valid for this ray.
			boost::optional< std::pair<T,T> > p = line<T>(start, direction).closestRayT(l);
			return (p && isValid(p->first)) ? p : boost::optional< std::pair<T,T> >();
		}
		///////////////////////////////////////////////////////////////////////////////////////////

		/**
		 * Calculate the closest ray between this ray and a line.
		 */
		boost::optional< ray<T> > closestRay(const line<T>& l) const
		{
			// Create a ray that starts on our ray and ends on the other line
			boost::optional< std::pair<T,T> > p = closestRayT(l);
			return (p) ? ray<T>(point(p->first), l.point(p->second)) : boost::optional< ray<T> >();
		}
		///////////////////////////////////////////////////////////////////////////////////////////

		/**
		 * Calculate the closest ray between this ray and the other one.
		 * This function actually returns the parameters for both this ray
		 * and the other one.
		 */
		boost::optional< std::pair<T,T> > closestRayT(const ray<T>& r) const
		{
			// Call the implementation of ray vs line and test if the returned
			// parameter is valid for the other ray (just like ray vs line does with this
			// parameter).
			boost::optional< std::pair<T,T> > p = closestRayT(line<T>(r.start, r.direction));
			return (p && r.isValid(p->second)) ? p : boost::optional< std::pair<T,T> >();
		}
		///////////////////////////////////////////////////////////////////////////////////////////

		/**
		 * Calculate the closest ray between this ray and the other one.
		 */
		boost::optional< ray<T> > closestRay(const ray<T>& r) const
		{
			// Create a ray that starts on our ray and ends on the other one.
			boost::optional< std::pair<T,T> > p = closestRayT(r);
			return (p) ? ray<T>(point(p->first), r.point(p->second)) : boost::optional< ray<T> >();
		}
		///////////////////////////////////////////////////////////////////////////////////////////
	};
	///////////////////////////////////////////////////////////////////////////////////////////////




	/**
	 * Transform a Ray by a 4x4 matrix.
	 *
	 * @return   the transformed cylinder
	 */
	template <typename T>
	ray<T> transform(const matrix4x4<T>& lhs, const ray<T>& rhs)
	{
		return ray<T>(lhs.transform(rhs.start), lhs.transform(rhs.end));
	}
	///////////////////////////////////////////////////////////////////////////////////////////////




	/**
	 * In order to compile a binary that actually contains the code for our Lines,
	 * we have to explicitly instantiate the template Lines and export those:
	 */
	template struct MATH_API intersectable< ray<float> >;
	template struct MATH_API intersectable< ray<double> >;
	template class MATH_API ray<float>;
	template class MATH_API ray<double>;
}
///////////////////////////////////////////////////////////////////////////////////////////////////
